# @Time : 2021/6/1 15:30
# @Author : Fioman 
# @Phone : 13149920693
import queue
import socket
import threading

import gevent

from tools.hd_logger import HdLogger
from db_tools.db_handler import DbHandler
import settings
from hd_socket.xml_parser import *
from tools.send_to_ui_tools import send_eap_msg_state_to_ui

"""
负责和eap通信的模块,在连接eap的时候才生效.
"""

BUFFER_SIZE = 16384  # 设置读物的一次BUFFER_SIZE的大小.16K


class HdClient(object):
    def __init__(self):
        self.logger = HdLogger()
        self.db = DbHandler()
        self.reload_eap_params()
        self.eapOnline = False
        self.loopDelay = 0.5
        self.msgQueueToEap = queue.Queue()  # 发送给EAP的消息队列,只保存消息的类型.
        self.deviceStateMsgHeader = "HdDeviceStates"
        self.alarmMsgHeader = "HdAlarmReport"
        self.recipeMsgHeader = "ParamIssue"
        self.requestRecipeHeader = "RequestRecipe"
        self.eapRecipeArgsReply = "ParamIssueReply"
        self.recipeScanName = ""  # 前端界面扫描到的工单内容
        self.msgShowList = []  # 前端界面显示消息列表,只维护固定个数的消息

        self.connectThread = threading.Thread(target=self.connect_listener_for_eap, name="connectThread")
        self.recvMsgThread = threading.Thread(target=self.receive_message_from_eap, name="recvMsgThread")
        self.sendMsgThread = threading.Thread(target=self.send_message_to_eap, name="sendMsgThread")
        self.deviceStateListenerThread = threading.Thread(target=self.device_state_listener, name="deviceStateListenerThread")
        self.alarmStateListenerThread = threading.Thread(target=self.alarm_state_listener, name="alarmStateListenerThread")
        self.managerThread = threading.Thread(target=self.manager_thread, name='managerThread')

    def connect_listener_for_eap(self):
        """
        监控eap的连接.
        :return:
        """
        tcpServerSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        tcpServerSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.modify_buffer_size(tcpServerSock)
        tcpServerSock.bind((self.deviceIp, self.port))
        tcpServerSock.listen(5)

        while True:
            try:
                print("waiting for connection...")
                tcpClientSock, addr = tcpServerSock.accept()
                print("...connected from: {}".format(addr))
                self.sock = tcpClientSock
                self.eapOnline = True
                msgVal = {
                    "val": "updateEapIsOnline",
                    "eapIsOnline": self.eapOnline
                }
                send_eap_msg_state_to_ui(msgVal)
            except Exception as e:
                errorInfo = "in connect_listener_for_eap() error: {}".format(str(e))
                print(errorInfo)
                self.logger.debug(errorInfo)
                self.sock = None
                self.eapOnline = False
                msgVal = {
                    "val": "updateEapIsOnline",
                    "eapIsOnline": self.eapOnline
                }
                send_eap_msg_state_to_ui(msgVal)

    def reload_eap_params(self):
        """
        初始化eap的初始状态信息.
        :return:
        """
        res, data = self.db.get_eap_params()
        if res != "ok":
            return data
        self.deviceIp = data.get("deviceIp")
        self.eapIp = data.get("eapIp")
        self.port = int(data.get("port"))
        self.deviceUpdateDuration = int(data.get("deviceUpdateDuration"))
        self.alarmUpdateDuration = int(data.get("alarmUpdateDuration"))
        self.msgShowLength = int(data.get("msgShowLength"))

    def device_state_listener(self):
        """
        设备状态表轮询间隔.
        :return:
        """
        while settings.IS_MAIN_ALIVE:
            if self.eapOnline:
                self.msgQueueToEap.put(self.deviceStateMsgHeader)
                if self.deviceUpdateDuration > 0.5:
                    gevent.sleep(self.deviceUpdateDuration)
                else:
                    gevent.sleep(0.5)
            else:
                gevent.sleep(self.loopDelay)

    def alarm_state_listener(self):
        """
        设备报警消息的轮询间隔
        :return:
        """
        while settings.IS_MAIN_ALIVE:
            if self.eapOnline:
                self.msgQueueToEap.put(self.alarmMsgHeader)
                if self.alarmUpdateDuration > 0.5:
                    gevent.sleep(self.alarmUpdateDuration)
                else:
                    gevent.sleep(0.5)
            else:
                gevent.sleep(0.5)

    def send_message_to_eap(self):
        """
        发送消息到eap,将要发送的消息放到队列中去,队列中只是消息的类型,然后消息的内容,去读xml文件
        :return:
        """
        while settings.IS_MAIN_ALIVE:
            if self.eapOnline:
                if not self.msgQueueToEap.empty():
                    msgHeader = self.msgQueueToEap.get()
                    res = self.send_data_to_eap(msgHeader)
                    if res == "ok":
                        print("消息: {} 发送成功".format(msgHeader))
                    else:
                        print("消息: {} 发送失败: {}".format(msgHeader, res))
                    gevent.sleep(self.loopDelay)
                else:
                    gevent.sleep(self.loopDelay)
            else:
                gevent.sleep(self.loopDelay)

    def receive_message_from_eap(self):
        """
        处理从eap接收到的消息内容
        :return:
        """
        while settings.IS_MAIN_ALIVE:
            if self.eapOnline:
                try:
                    recvData = self.sock.recv(BUFFER_SIZE)
                    print("recvMsgLen = {}".format(len(recvData)))
                    if recvData:
                        msgHeader, bodyDict, retCode = parse_recv_msg(recvData)
                        print("从EAP接收到消息：msgName = {},bodyDict = {},retCode = {}".format(msgHeader, bodyDict, retCode))
                        self.add_message_to_msgshow("recv", [msgHeader, bodyDict, retCode])
                        self.deal_recv_message(msgHeader, bodyDict, retCode)
                        gevent.sleep(self.loopDelay)
                        self.eapOnline = True
                        msgVal = {
                            "val": "updateEapIsOnline",
                            "eapIsOnline": self.eapOnline
                        }
                        send_eap_msg_state_to_ui(msgVal)
                except Exception as e:
                    print("in receive_message_from_eap() error : {}".format(str(e)))
                    self.sock = None
                    self.eapOnline = False
                    msgVal = {
                        "val": "updateEapIsOnline",
                        "eapIsOnline": self.eapOnline
                    }
                    send_eap_msg_state_to_ui(msgVal)
            else:
                gevent.sleep(self.loopDelay)

    def web_request_clear_msgshow(self):
        """
        界面上点击清空显示的消息
        :return:
        """
        self.msgShowList = []
        msgVal = {
            "val": "updateEapMsg",
            "eapMsgInfo": self.msgShowList
        }
        send_eap_msg_state_to_ui(msgVal)
        return "ok"

    def deal_recv_message(self, msgHeader, bodyDict, retCode):
        """
        处理EAP发送过来的消息
        :param msgHeader: 消息头的内容
        :param bodyDict: 字典的内容
        :param retCode: 恢复码
        :return:
        """
        if msgHeader == self.recipeMsgHeader:
            # 收到了参数下发的请求
            query = {
                "key": "1"
            }
            newVal = {
                "$set": bodyDict
            }
            res, info = self.db.update_data(self.db.recipeParamsFromEapTable, query, newVal)
            if res != "ok":
                errorInfo = "in deal_recv_message() 工单下发的参数保存失败: {}".format(info)
                print(errorInfo)
                self.logger.debug(errorInfo)
            debugInfo = "收到配方参数下发的消息,配方保存到数据库成功!bodyDict: {},retCode: {}".format(bodyDict, retCode)
            self.msgQueueToEap.put(self.eapRecipeArgsReply)
            print(debugInfo)
            self.logger.debug(debugInfo)
            msgValue = {
                "val": "updateEapParamsState",
            }
            send_eap_msg_state_to_ui(msgValue)
        else:
            errorInfo = "in deal_recv_message() error: 消息名称错误,messagename: {} 为未知消息".format(msgHeader)
            print(errorInfo)
            self.logger.debug(errorInfo)

    def send_data_to_eap(self, msgHeader):
        """
        根据消息类型去封装要发送的内容,发送给eap
        :param msgHeader: 消息的类型
        :return:
        """
        if msgHeader == self.deviceStateMsgHeader:
            dataBytes = self.get_device_state_data()
        elif msgHeader == self.alarmMsgHeader:
            dataBytes = self.get_alarm_info_data()
        elif msgHeader == self.requestRecipeHeader:
            dataBytes = self.get_request_recipe_data()
        elif msgHeader == self.eapRecipeArgsReply:
            dataBytes = self.get_request_recipe_reply_data()
        else:
            dataBytes = None

        if dataBytes is not None:
            msgSend = bytes(chr(2), encoding="ascii") + dataBytes + bytes(chr(3), encoding="ascii")
            self.sock.sendall(msgSend)
            return "ok"
        else:
            return "要发送的数据为None,消息发送失败"

    def get_alarm_info_data(self):
        """
        获取设备的报警信息表,注意报警信息里面的值,如果有报警,就设置为报警字符串,如果没有报警就置为 "0".
        :return:
        """
        res, data = self.db.get_alarm_info_to_eap()
        if res != "ok":
            return None
        bodyDict = data
        bodyDict.pop("key")
        newDict = {}
        for key, val in bodyDict.items():
            if val != "0":
                newDict[key] = val

        if len(newDict.keys()) <= 0:
            return None
        retDict = {
            "returncode": "0",
            "returnmessage": ""
        }
        self.add_message_to_msgshow("send", [self.alarmMsgHeader, newDict, retDict])
        pack_send_msg(self.alarmMsgHeader, newDict, retDict)
        with open(MSG_SEND_PATH, "rb") as f:
            data = f.read()
        return data

    def get_request_recipe_data(self):
        """
        获取工单配方参数请求的消息
        :return:
        """
        bodyDict = {
            "lot": self.recipeScanName
        }
        retDict = {
            "returncode": "0",
            "returnmessage": ""
        }
        self.add_message_to_msgshow("send", [self.requestRecipeHeader, bodyDict, retDict])
        pack_send_msg(self.requestRecipeHeader, bodyDict, retDict)
        with open(MSG_SEND_PATH, "rb") as f:
            data = f.read()
        return data

    def get_request_recipe_reply_data(self):
        bodyDict = {
        }
        retDict = {
            "returncode": "1",
            "returnmessage": "配方参数下发失败，数据解析失败！"
        }
        self.add_message_to_msgshow("send", [self.eapRecipeArgsReply, bodyDict, retDict])
        pack_send_msg(self.eapRecipeArgsReply, bodyDict, retDict)
        with open(MSG_SEND_PATH, "rb") as f:
            data = f.read()
        return data

    def get_device_state_data(self):
        """
        获取设备状态的数据内容
        :return:
        """
        res, data = self.db.get_device_state_to_eap()
        if res != "ok":
            return None
        bodyDict = data  # 将key这个值删除掉.
        bodyDict.pop("key")
        retDict = {
            "returncode": "0",
            "returnmessage": ""
        }
        self.add_message_to_msgshow("send", [self.deviceStateMsgHeader, bodyDict, retDict])
        pack_send_msg(self.deviceStateMsgHeader, bodyDict, retDict)
        with open(MSG_SEND_PATH, "rb") as f:
            data = f.read()
        return data

    @staticmethod
    def get_time_now_string_only_hour():
        """
        获取时间字符串,只显示时间,不显示日期
        :return:
        """
        return time.strftime("%H:%M:%S", time.localtime())

    def add_message_to_msgshow(self, msgType, msgInfo):
        """
        将消息加入到展示到前端界面上的消息内容进行展示,分为发送和接收,msgInfo里面是消息的具体内容
        :param msgType: 消息的类型  "recv" 表示从EAP接收到消息, "send" 表示发送给EAP的消息内容
        :param msgInfo: 消息的内容,消息名称,具体内容,结果码.
        :return:
        """
        msgSend = []
        if msgType == "recv":
            msgHead = " 从EAP接收到消息"
        else:
            msgHead = " 发送消息到EAP"

        msgHead = self.get_time_now_string_only_hour() + msgHead
        msgSend.append(msgHead)
        msgBody = ["消息类型: {}".format(msgInfo[0]), "消息主体: {}".format(msgInfo[1]), "消息结果码: {}".format(msgInfo[2])]
        msgSend.append(msgBody)

        # 只显示前面几条内容
        if len(self.msgShowList) >= self.msgShowLength:
            self.msgShowList.pop(0)

        self.msgShowList.append(msgSend)
        print("msgShowList : {}".format(self.msgShowList))
        msgSend2Ui = self.msgShowList[::-1]
        msgVal = {
            "val": "updateEapMsg",
            "eapMsgInfo": msgSend2Ui
        }
        send_eap_msg_state_to_ui(msgVal)

    def web_request_send_eap_recipe_args_request(self, recipeName):
        """
        请求配方参数下发
        :param recipeName:配方参数名称
        :return:
        """
        if recipeName == "":
            return "配方名称为空"
        self.msgQueueToEap.put(self.requestRecipeHeader)
        self.recipeScanName = recipeName
        return "ok"

    def web_request_get_eap_params(self):
        """
        前端获取eap参数表信息
        :return:
        """
        res, data = self.db.get_eap_params()
        return [data] if res == "ok" else []

    def web_request_get_eap_recipe_params(self):
        """
        前端获取Eap参数下发的信息表
        :return:
        """
        res, data = self.db.get_eap_recipe_params()
        return [data] if res == "ok" else []

    def web_request_save_eap_params(self, newData):
        """
        界面上点击了保存eap参数信息的按钮,对eap的参数进行保存
        :param newData: 新的值
        :return:
        """
        query = {
            "key": "1"
        }
        newValues = {
            "$set": newData
        }
        res, info = self.db.update_data(self.db.eapParamsTable, query, newValues)
        if res != "ok":
            return info
        self.reload_eap_params()
        return "ok"

    def web_request_connect_eap(self):
        """
        前端界面点击了eap的连接.
        :return:
        """
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.modify_buffer_size(self.sock)

        try:
            self.sock.connect((self.eapIp, self.port))
            self.eapOnline = True
            return "ok"
        except Exception as e:
            errorInfo = "in web_request_connect_eap() 连接eap失败: {}".format(str(e))
            print(errorInfo)
            self.logger.debug(errorInfo)
            self.sock = None
            self.eapOnline = False
            return "连接超时,请检查网线是否插好以及EAP的IP地址是否正确!"

    def web_request_close_eap_connect(self):
        """
        前端界面点击了关闭EAP连接
        :return:
        """
        if self.sock is not None:
            self.sock.close()
            self.sock = None
            self.eapOnline = False
        return "ok"

    @staticmethod
    def modify_buffer_size(hdSock):
        """
        设置一次读取的buffer大小,提高性能
        :param hdSock: 使用的socket套接字
        :return:
        """
        # 获取当前套接字关联的选项,socket.SOL_SOCKET,正在使用的socket,socket.SOCK_STREAM发送缓存区的大小
        hdSock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF)
        hdSock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)

        # 设置TCP套接字关联的选项
        # socket.TCP_NODELAY TCP层套接口选项
        # socket.TCP_NODELAY.表示禁用了Nagle,允许小包发送.不用等很多的包折合一起再发送的问题.适用于数据量小的项目.
        hdSock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)

        # 设置发送缓存区套接字关联的选项
        hdSock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, BUFFER_SIZE)
        # 设置接收缓存区套接字关联的选项
        hdSock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, BUFFER_SIZE)

    def manager_thread(self):
        print("is_main_alive: {}".format(settings.IS_MAIN_ALIVE))
        while settings.IS_MAIN_ALIVE:
            try:
                if not self.connectThread.is_alive():
                    self.connectThread = threading.Thread(target=self.connect_listener_for_eap, name="connectThread")
                    self.connectThread.setDaemon(True)
                    self.connectThread.start()
                if self.eapOnline:
                    if not self.recvMsgThread.is_alive():
                        self.recvMsgThread = threading.Thread(target=self.receive_message_from_eap, name="recvMsgThread")
                        self.recvMsgThread.setDaemon(True)
                        self.recvMsgThread.start()

                    if not self.sendMsgThread.is_alive():
                        self.sendMsgThread = threading.Thread(target=self.send_message_to_eap, name="sendMsgThread")
                        self.sendMsgThread.setDaemon(True)
                        self.sendMsgThread.start()

                    if not self.deviceStateListenerThread.is_alive():
                        self.deviceStateListenerThread = threading.Thread(target=self.device_state_listener, name="deviceStateListenerThread")
                        self.deviceStateListenerThread.setDaemon(True)
                        self.deviceStateListenerThread.start()

                    if not self.alarmStateListenerThread.is_alive():
                        self.alarmStateListenerThread = threading.Thread(target=self.alarm_state_listener, name="alarmStateListenerThread")
                        self.alarmStateListenerThread.setDaemon(True)
                        self.alarmStateListenerThread.start()
                    gevent.sleep(2)
                else:
                    gevent.sleep(2)
            except Exception as e:
                errorInfo = "in HdClient.thread_manager() error: {}".format(str(e))
                print(errorInfo)
                self.logger.debug(errorInfo)
